﻿//
// (C) Copyright 2009 Irantha Suwandarathna (iranthas@hotmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using EffiProz.Core.Navigators;
using EffiProz.Core.Errors;
using EffiProz.Core.RowIO;
using EffiProz.Core.Lib;
using EffiProz.Core.Lib.IO;
using EffiProz.Core.DataTypes;

namespace EffiProz.Core.Results
{
    public class Result
    {
        public static Result updateZeroResult = newResult(ResultConstants.UPDATECOUNT);
        public static Result updateOneResult = newResult(ResultConstants.UPDATECOUNT);
        public static Result updateTwoResult = newResult(ResultConstants.UPDATECOUNT);


        public static ResultMetaData sessionAttributesMetaData =
            ResultMetaData.newResultMetaData(SessionInfoTypeConsts.INFO_LIMIT);

        static Result()
        {
            updateOneResult.setUpdateCount(1);
            updateTwoResult.setUpdateCount(2);
            for (int i = 0; i < SessionInfoTypeConsts.INFO_LIMIT; i++)
            {
                sessionAttributesMetaData.columns[i] = new ColumnBase(null, null,
                        null, null);
            }

            sessionAttributesMetaData.columns[SessionInfoTypeConsts.INFO_ID].setType(
                SqlType.SQL_INTEGER);
            sessionAttributesMetaData.columns[SessionInfoTypeConsts.INFO_INTEGER].setType(
                SqlType.SQL_INTEGER);
            sessionAttributesMetaData.columns[SessionInfoTypeConsts.INFO_BOOLEAN].setType(
                SqlType.SQL_BOOLEAN);
            sessionAttributesMetaData.columns[SessionInfoTypeConsts.INFO_VARCHAR].setType(
                SqlType.SQL_VARCHAR);
            sessionAttributesMetaData.prepareData();
        }

        private static ResultMetaData emptyMeta =
            ResultMetaData.newResultMetaData(0);
        public static Result emptyGeneratedResult =
            Result.newDataResult(emptyMeta);

        // type of result
        protected byte mode;

        // database ID
        public int databaseID;

        // session ID
        public long sessionID;

        // result id
        private long id;

        // database name for new connection
        private String databaseName;

        // user / password for new connection
        // error strings in error results
        private String mainString;
        private String subString;
        private String zoneString;

        // vendor error code
        int errorCode;

        // the exception if this is an error
        private CoreException exception;

        // prepared statement id
        long statementID;

        // statement type based on whether it returns an update count or a result set
        // type of session info requested
        int statementReturnType;

        // max rows (out)
        // update count (in)
        // fetch part result count (in)
        // time zone seconds (connect)
        private int updateCount;

        // fetch size (in)
        private int fetchSize;

        // secondary result
        private Result chainedResult;

        //
        private int lobCount;
        ResultLob lobResults;

        /** A Result object's metadata */
        public ResultMetaData metaData;

        /** Additional meta data for parameters used in PREPARE_ACK results */
        public ResultMetaData parameterMetaData;

        /** Additional meta data for required generated columns */
        public ResultMetaData generatedMetaData;

        //
        public int rsScrollability;
        public int rsConcurrency;
        public int rsHoldability;

        //
        public int queryTimeout;

        //
        int generateKeys = ResultConstants.RETURN_NO_GENERATED_KEYS;

        // simple value for PSM
        Object valueData;

        //
        Statement statement;

        public static Result newResult(RowSetNavigator nav)
        {

            Result result = new Result();

            result.mode = ResultConstants.DATA;
            result.navigator = nav;

            return result;
        }

        public static Result newResult(int type)
        {

            RowSetNavigator navigator = null;
            Result result = null;

            switch (type)
            {

                case ResultConstants.CALL_RESPONSE:
                case ResultConstants.EXECUTE:
                    navigator = new RowSetNavigatorClient(1);
                    break;

                case ResultConstants.UPDATE_RESULT:
                    navigator = new RowSetNavigatorClient(1);
                    break;

                case ResultConstants.BATCHEXECUTE:
                case ResultConstants.BATCHEXECDIRECT:
                    navigator = new RowSetNavigatorClient(4);
                    break;

                case ResultConstants.SETSESSIONATTR:
                case ResultConstants.PARAM_METADATA:
                    navigator = new RowSetNavigatorClient(1);
                    break;

                case ResultConstants.BATCHEXECRESPONSE:
                    navigator = new RowSetNavigatorClient(4);
                    break;

                case ResultConstants.DATA:
                case ResultConstants.DATAHEAD:
                case ResultConstants.DATAROWS:
                    break;

                case ResultConstants.LARGE_OBJECT_OP:
                    throw Error.runtimeError(ErrorCode.U_S0500, "Result");

            }

            result = new Result();
            result.mode = (byte)type;
            result.navigator = navigator;

            return result;
        }

        public static Result newResult(DataInputStream dataInput,
                                       RowInputBinary _in)
        {
            return newResult(null, dataInput.ReadByte(), dataInput, _in);
        }

        public static Result newResult(Session session, int mode,
                                       DataInputStream dataInput,
                                       RowInputBinary _in)
        {

            try
            {
                if (mode == ResultConstants.LARGE_OBJECT_OP)
                {
                    return ResultLob.newLob(dataInput, false);
                }

                Result result = newResult(session, dataInput, _in, mode);

                return result;
            }
            catch (IOException)
            {
                throw Error.error(ErrorCode.X_08000);
            }
        }

        public void readAdditionalResults(SessionInterface session,
                                          DataInputStream inputStream,
                                          RowInputBinary _in)
        {

            setSession(session);

            Result currentResult = this;
            bool hasLob = false;

            while (true)
            {
                int addedResultMode = inputStream.ReadByte();

                if (addedResultMode == ResultConstants.LARGE_OBJECT_OP)
                {
                    ResultLob resultLob = ResultLob.newLob(inputStream, false);

                    if (session is Session)
                    {
                        ((Session)session).allocateResultLob(resultLob,
                                                              inputStream);
                    }

                    currentResult.addLobResult(resultLob);

                    hasLob = true;

                    continue;
                }

                if (hasLob)
                {
                    hasLob = false;

                    if (session is Session)
                    {
                        ((Session)session).registerResultLobs(currentResult);
                    }
                }

                if (addedResultMode == ResultConstants.NONE)
                {
                    return;
                }

                currentResult = newResult(null, inputStream, _in, addedResultMode);

                addChainedResult(currentResult);
            }
        }

        private static Result newResult(Session session, DataInputStream dataInput,
                                        RowInputBinary _in,
                                        int mode)
        {
        
            Result result = newResult(mode);

          
            int length = dataInput.ReadInt();

            _in.resetRow(0, length);

            byte[] byteArray = _in.getBuffer();
            int offset = 4;

            dataInput.ReadFully(byteArray, offset, length - offset);

            switch (mode)
            {

                case ResultConstants.GETSESSIONATTR:
                    result.statementReturnType = _in.readByte();
                    break;

                case ResultConstants.DISCONNECT:
                case ResultConstants.RESETSESSION:
                case ResultConstants.STARTTRAN:
                    break;

                case ResultConstants.PREPARE:
                    result.setStatementType(_in.readByte());

                    result.mainString = _in.readString();
                    result.rsScrollability = _in.readShort();
                    result.rsConcurrency = _in.readShort();
                    result.rsHoldability = _in.readShort();
                    result.generateKeys = _in.readByte();

                    if (result.generateKeys == ResultConstants
                            .RETURN_GENERATED_KEYS_COL_NAMES || result
                            .generateKeys == ResultConstants
                            .RETURN_GENERATED_KEYS_COL_INDEXES)
                    {
                        result.generatedMetaData = new ResultMetaData(_in);
                    }
                    break;

                case ResultConstants.CLOSE_RESULT:
                    result.id = _in.readLong();
                    break;

                case ResultConstants.FREESTMT:
                    result.statementID = _in.readLong();
                    break;

                case ResultConstants.EXECDIRECT:
                    result.updateCount = _in.readInt();
                    result.fetchSize = _in.readInt();
                    result.statementReturnType = _in.readByte();
                    result.mainString = _in.readString();
                    result.rsScrollability = _in.readShort();
                    result.rsConcurrency = _in.readShort();
                    result.rsHoldability = _in.readShort();
                    result.queryTimeout = _in.readShort();
                    result.generateKeys = _in.readByte();

                    if (result.generateKeys == ResultConstants
                            .RETURN_GENERATED_KEYS_COL_NAMES || result
                            .generateKeys == ResultConstants
                            .RETURN_GENERATED_KEYS_COL_INDEXES)
                    {
                        result.generatedMetaData = new ResultMetaData(_in);
                    }
                    break;

                case ResultConstants.CONNECT:
                    result.databaseName = _in.readString();
                    result.mainString = _in.readString();
                    result.subString = _in.readString();
                    result.zoneString = _in.readString();
                    result.updateCount = _in.readInt();
                    break;

                case ResultConstants.ERROR:
                    result.mainString = _in.readString();
                    result.subString = _in.readString();
                    result.errorCode = _in.readInt();
                    break;

                case ResultConstants.CONNECTACKNOWLEDGE:
                    result.databaseID = _in.readInt();
                    result.sessionID = _in.readLong();
                    break;

                case ResultConstants.UPDATECOUNT:
                    result.updateCount = _in.readInt();
                    break;

                case ResultConstants.ENDTRAN:
                    {
                        int type = _in.readInt();

                        result.setActionType(type);                     // endtran type

                        switch (type)
                        {

                            case ResultConstants.TX_SAVEPOINT_NAME_RELEASE:
                            case ResultConstants.TX_SAVEPOINT_NAME_ROLLBACK:
                                result.mainString = _in.readString();    // savepoint name
                                break;

                            case ResultConstants.TX_COMMIT:
                            case ResultConstants.TX_ROLLBACK:
                            case ResultConstants.TX_COMMIT_AND_CHAIN:
                            case ResultConstants.TX_ROLLBACK_AND_CHAIN:
                                break;

                            default:
                                throw Error.runtimeError(ErrorCode.U_S0500, "Result");
                        }

                        break;
                    }
                case ResultConstants.SETCONNECTATTR:
                    {
                        int type = _in.readInt();                        // attr type

                        result.setConnectionAttrType(type);

                        switch (type)
                        {

                            case ResultConstants.SQL_ATTR_SAVEPOINT_NAME:
                                result.mainString = _in.readString();    // savepoint name
                                break;

                            //  case ResultConstants.SQL_ATTR_AUTO_IPD :
                            //      - always true
                            //  default: throw - case never happens
                            default:
                                throw Error.runtimeError(ErrorCode.U_S0500, "Result");
                        }

                        break;
                    }
                case ResultConstants.PREPARE_ACK:
                    result.statementReturnType = _in.readByte();
                    result.statementID = _in.readLong();
                    result.rsScrollability = _in.readShort();
                    result.rsConcurrency = _in.readShort();
                    result.rsHoldability = _in.readShort();
                    result.metaData = new ResultMetaData(_in);
                    result.parameterMetaData = new ResultMetaData(_in);
                    break;

                case ResultConstants.CALL_RESPONSE:
                    result.updateCount = _in.readInt();
                    result.fetchSize = _in.readInt();
                    result.statementID = _in.readLong();
                    result.rsScrollability = _in.readShort();
                    result.rsConcurrency = _in.readShort();
                    result.rsHoldability = _in.readShort();
                    result.metaData = new ResultMetaData(_in);

                    result.navigator.readSimple(_in, result.metaData);
                    break;

                case ResultConstants.EXECUTE:
                    result.updateCount = _in.readInt();
                    result.fetchSize = _in.readInt();
                    result.statementID = _in.readLong();
                    result.rsScrollability = _in.readShort();
                    result.rsConcurrency = _in.readShort();
                    result.rsHoldability = _in.readShort();
                    result.queryTimeout = _in.readShort();

                    Statement statement =
                        session.statementManager.getStatement(
                            session, result.statementID);

                    result.statement = statement;
                    result.metaData = result.statement.getParametersMetaData();

                    result.navigator.readSimple(_in, result.metaData);
                    break;

                case ResultConstants.UPDATE_RESULT:
                    {
                        result.id = _in.readLong();

                        int type = _in.readInt();

                        result.setActionType(type);

                        result.metaData = new ResultMetaData(_in);

                        result.navigator.read(_in, result.metaData);

                        break;
                    }
                case ResultConstants.BATCHEXECRESPONSE:
                case ResultConstants.BATCHEXECUTE:
                case ResultConstants.BATCHEXECDIRECT:
                case ResultConstants.SETSESSIONATTR:
                    {
                        result.updateCount = _in.readInt();
                        result.fetchSize = _in.readInt();
                        result.statementID = _in.readLong();
                        result.queryTimeout = _in.readShort();
                        result.metaData = new ResultMetaData(_in);

                        result.navigator.readSimple(_in, result.metaData);

                        break;
                    }
                case ResultConstants.PARAM_METADATA:
                    {
                        result.metaData = new ResultMetaData(_in);

                        result.navigator.read(_in, result.metaData);

                        break;
                    }
                case ResultConstants.REQUESTDATA:
                    {
                        result.id = _in.readLong();
                        result.updateCount = _in.readInt();
                        result.fetchSize = _in.readInt();

                        break;
                    }
                case ResultConstants.DATAHEAD:
                case ResultConstants.DATA:
                    {
                        result.id = _in.readLong();
                        result.updateCount = _in.readInt();
                        result.fetchSize = _in.readInt();
                        result.rsScrollability = _in.readShort();
                        result.rsConcurrency = _in.readShort();
                        result.rsHoldability = _in.readShort();
                        result.metaData = new ResultMetaData(_in);
                        result.navigator = new RowSetNavigatorClient();

                        result.navigator.read(_in, result.metaData);

                        break;
                    }
                case ResultConstants.DATAROWS:
                    {
                        result.metaData = new ResultMetaData(_in);
                        result.navigator = new RowSetNavigatorClient();

                        result.navigator.read(_in, result.metaData);

                        break;
                    }
                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "Result");
            }

            return result;
        }

        /**
         * For interval PSM return values
         */
        public static Result newPSMResult(int type, String label, Object value)
        {

            Result result = newResult(ResultConstants.VALUE);

            result.errorCode = type;
            result.mainString = label;
            result.valueData = value;

            return result;
        }

        /**
         * For SQLPREPARE
         * For parparation of SQL parepared statements.
         */
        public static Result newPrepareStatementRequest()
        {
            return newResult(ResultConstants.PREPARE);
        }

        /**
         * For SQLEXECUTE
         * For execution of SQL prepared statements.
         * The parameters are set afterwards as the Result is reused
         */
        public static Result newPreparedExecuteRequest(SqlType[] types,
                long statementId)
        {

            Result result = newResult(ResultConstants.EXECUTE);

            result.metaData = ResultMetaData.newSimpleResultMetaData(types);
            result.statementID = statementId;

            result.navigator.add(new object[0]);

            return result;
        }

        /**
         * For CALL_RESPONSE
         * For execution of SQL callable statements.
         */
        public static Result newCallResponse(SqlType[] types, long statementId,
                                             Object[] values)
        {

            Result result = newResult(ResultConstants.CALL_RESPONSE);

            result.metaData = ResultMetaData.newSimpleResultMetaData(types);
            result.statementID = statementId;

            result.navigator.add(values);

            return result;
        }

        /**
         * For UPDATE_RESULT
         * The parameters are set afterwards as the Result is reused
         */
        public static Result newUpdateResultRequest(SqlType[] types, long id)
        {

            Result result = newResult(ResultConstants.UPDATE_RESULT);

            result.metaData = ResultMetaData.newUpdateResultMetaData(types);
            result.id = id;

            result.navigator.add(new Object[] { });

            return result;
        }

        /**
         * For UPDATE_RESULT results
         * The parameters are set by this method as the Result is reused
         */
        public void setPreparedResultUpdateProperties(Object[] parameterValues)
        {

            if (navigator.getSize() == 1)
            {
                ((RowSetNavigatorClient)navigator).setData(0, parameterValues);
            }
            else
            {
                navigator.clear();
                navigator.add(parameterValues);
            }
        }

        /**
         * For SQLEXECUTE results
         * The parameters are set by this method as the Result is reused
         */
        public void setPreparedExecuteProperties(Object[] parameterValues,
                int maxRows, int fetchSize)
        {

            mode = ResultConstants.EXECUTE;

            if (navigator.getSize() == 1)
            {
                ((RowSetNavigatorClient)navigator).setData(0, parameterValues);
            }
            else
            {
                navigator.clear();
                navigator.add(parameterValues);
            }

            updateCount = maxRows;
            this.fetchSize = fetchSize;
        }

        /**
         * For BATCHEXECUTE
         */
        public void setBatchedPreparedExecuteRequest()
        {

            mode = ResultConstants.BATCHEXECUTE;

            ((RowSetNavigatorClient)navigator).clear();

            updateCount = 0;
            this.fetchSize = 0;
        }

        public void addBatchedPreparedExecuteRequest(Object[] parameterValues)
        {
            ((RowSetNavigatorClient)navigator).add(parameterValues);
        }

        /**
         * For BATCHEXECDIRECT
         */
        public static Result newBatchedExecuteRequest()
        {

            SqlType[] types = new SqlType[] { SqlType.SQL_VARCHAR };
            Result result = newResult(ResultConstants.BATCHEXECDIRECT);

            result.metaData = ResultMetaData.newSimpleResultMetaData(types);

            return result;
        }

        /**
         * For BATCHEXERESPONSE for a BATCHEXECUTE or BATCHEXECDIRECT
         */
        public static Result newBatchedExecuteResponse(int[] updateCounts,
                Result generatedResult, Result e)
        {

            Result result = newResult(ResultConstants.BATCHEXECRESPONSE);

            result.addChainedResult(generatedResult);
            result.addChainedResult(e);

            SqlType[] types = new SqlType[] { SqlType.SQL_INTEGER };

            result.metaData = ResultMetaData.newSimpleResultMetaData(types);

            Object[][] table = new Object[updateCounts.Length][];

            for (int i = 0; i < updateCounts.Length; i++)
            {
                table[i] = new Object[] { updateCounts[i] };
            }

            ((RowSetNavigatorClient)result.navigator).setData(table);

            return result;
        }

        public static Result newResetSessionRequest()
        {

            Result result = newResult(ResultConstants.RESETSESSION);

            return result;
        }

        public static Result newConnectionAttemptRequest(String user,
                String password, String database, String zoneString,
                int timeZoneSeconds)
        {

            Result result = newResult(ResultConstants.CONNECT);

            result.mainString = user;
            result.subString = password;
            result.zoneString = zoneString;
            result.databaseName = database;
            result.updateCount = timeZoneSeconds;

            return result;
        }

        public static Result newConnectionAcknowledgeResponse(long sessionID,
                int databaseID)
        {

            Result result = newResult(ResultConstants.CONNECTACKNOWLEDGE);

            result.sessionID = sessionID;
            result.databaseID = databaseID;

            return result;
        }

        public static Result getUpdateCountResult(int count)
        {

            switch (count)
            {

                case 0:
                    return Result.updateZeroResult;

                case 1:
                    return Result.updateOneResult;

                case 2:
                    return Result.updateTwoResult;
            }

            Result result = newResult(ResultConstants.UPDATECOUNT);

            result.updateCount = count;

            return result;
        }

        public static Result newUpdateCountResult(ResultMetaData meta, int count)
        {

            Result result = newResult(ResultConstants.UPDATECOUNT);
            Result dataResult = newDataResult(meta);

            result.updateCount = count;

            result.addChainedResult(dataResult);

            return result;
        }

        public static Result newSingleColumnResult(ResultMetaData meta)
        {

            Result result = newResult(ResultConstants.DATA);

            result.metaData = meta;
            result.navigator = new RowSetNavigatorClient();

            return result;
        }

        public static Result newSingleColumnResult(String colName, SqlType type)
        {

            Result result = newResult(ResultConstants.DATA);

            result.metaData = ResultMetaData.newResultMetaData(1);
            result.metaData.columns[0] = new ColumnBase(null, null, null, colName);

            result.metaData.columns[0].setType(type);
            result.metaData.prepareData();

            //
            result.navigator = new RowSetNavigatorClient(8);

            return result;
        }

        public static Result newSingleColumnStringResult(String colName,
                String contents)
        {

            Result result = Result.newSingleColumnResult("OPERATION",
                SqlType.SQL_VARCHAR);
            StringReader lnr =
                new StringReader(contents);

            while (true)
            {
                String line = null;

                try
                {
                    line = lnr.ReadLine();
                }
                catch (Exception ) { }

                if (line == null)
                {
                    break;
                }

                result.getNavigator().add(new Object[] { line });
            }

            return result;
        }

        public static Result newPrepareResponse(Statement statement)
        {

            Result r = newResult(ResultConstants.PREPARE_ACK);

            r.statement = statement;
            r.statementID = statement.getID();

            int csType = statement.getType();

            r.statementReturnType =
                (csType == StatementTypes.SELECT_CURSOR || csType == StatementTypes
                    .CALL) ? StatementTypes.RETURN_RESULT
                           : StatementTypes.RETURN_COUNT;
            r.metaData = statement.getResultMetaData();
            r.parameterMetaData = statement.getParametersMetaData();

            return r;
        }

        public static Result newFreeStmtRequest(long statementID)
        {

            Result r = newResult(ResultConstants.FREESTMT);

            r.statementID = statementID;

            return r;
        }

        /**
         * For direct execution of SQL statements. The statement and other
         *  parameters are set afterwards as the Result is reused
         */
        public static Result newExecuteDirectRequest()
        {
            return newResult(ResultConstants.EXECDIRECT);
        }

        /**
         * For both EXECDIRECT and PREPARE
         */
        public void setPrepareOrExecuteProperties(String sql, int maxRows,
                int fetchSize, int statementReturnType, int timeout,
                int resultSetType, int resultSetConcurrency,
                int resultSetHoldability, int keyMode, int[] generatedIndexes,
                String[] generatedNames)
        {

            mainString = sql;
            updateCount = maxRows;
            this.fetchSize = fetchSize;
            this.statementReturnType = statementReturnType;
            this.queryTimeout = timeout;
            rsScrollability = resultSetType;
            rsConcurrency = resultSetConcurrency;
            rsHoldability = resultSetHoldability;
            generateKeys = keyMode;
            generatedMetaData =
                ResultMetaData.newGeneratedColumnsMetaData(generatedIndexes,
                    generatedNames);
        }

        public static Result newSetSavepointRequest(String name)
        {

            Result result;

            result = newResult(ResultConstants.SETCONNECTATTR);

            result.setConnectionAttrType(ResultConstants.SQL_ATTR_SAVEPOINT_NAME);
            result.setMainString(name);

            return result;
        }

        public static Result newRequestDataResult(long id, int offset, int count)
        {

            Result result = newResult(ResultConstants.REQUESTDATA);

            result.id = id;
            result.updateCount = offset;
            result.fetchSize = count;

            return result;
        }

        public static Result newDataResult(ResultMetaData md)
        {

            Result result = newResult(ResultConstants.DATA);

            result.navigator = new RowSetNavigatorClient();
            result.metaData = md;

            return result;
        }

        public void setDataResultConcurrency(bool isUpdatable)
        {
            rsConcurrency = isUpdatable ? ResultConstants.CONCUR_UPDATABLE
                                        : ResultConstants.CONCUR_READ_ONLY;
        }

        public void setDataResultConcurrency(int resultSetConcurrency)
        {
            rsConcurrency = resultSetConcurrency;
        }

        public void setDataResultHoldability(int resultSetHoldability)
        {
            rsHoldability = resultSetHoldability;
        }

        public void setDataResultScrollability(int resultSetScrollability)
        {
            rsScrollability = resultSetScrollability;
        }

        /**
         * For DATA
         */
        public void setDataResultProperties(int maxRows, int fetchSize,
                                            int resultSetScrollability,
                                            int resultSetConcurrency,
                                            int resultSetHoldability)
        {

            updateCount = maxRows;
            this.fetchSize = fetchSize;
            rsScrollability = resultSetScrollability;
            rsConcurrency = resultSetConcurrency;
            rsHoldability = resultSetHoldability;
        }

        public static Result newDataHeadResult(SessionInterface session,
                                               Result source, int offset,
                                               int count)
        {

            if (offset + count > source.navigator.getSize())
            {
                count = source.navigator.getSize() - offset;
            }

            Result result = newResult(ResultConstants.DATAHEAD);

            result.metaData = source.metaData;
            result.navigator = new RowSetNavigatorClient(source.navigator, offset,
                    count);

            result.navigator.setId(source.navigator.getId());
            result.setSession(session);

            result.rsConcurrency = source.rsConcurrency;
            result.rsHoldability = source.rsHoldability;
            result.rsScrollability = source.rsScrollability;
            result.fetchSize = source.fetchSize;

            return result;
        }

        public static Result newDataRowsResult(Result source, int offset,
                                               int count)
        {

            if (offset + count > source.navigator.getSize())
            {
                count = source.navigator.getSize() - offset;
            }

            Result result = newResult(ResultConstants.DATAROWS);

            result.id = source.id;
            result.metaData = source.metaData;
            result.navigator = new RowSetNavigatorClient(source.navigator, offset,
                    count);

            return result;
        }

        public static Result newDataRowsResult(RowSetNavigator navigator)
        {

            Result result = newResult(ResultConstants.DATAROWS);

            result.navigator = navigator;

            return result;
        }

        /**
         * Result structure used for set/get session attributes
         */
        public static Result newSessionAttributesResult()
        {

            Result result = newResult(ResultConstants.DATA);

            result.navigator = new RowSetNavigatorClient(1);
            result.metaData = sessionAttributesMetaData;

            result.navigator.add(new Object[SessionInfoTypeConsts.INFO_LIMIT]);

            return result;
        }

        public static Result newWarningResult(CoreException w)
        {

            Result result = newResult(ResultConstants.WARNING);

            result.mainString = w.getMessage();
            result.subString = w.getSQLState();
            result.errorCode = w.getErrorCode();

            return result;
        }

        public static Result newErrorResult(Exception t)
        {
            return newErrorResult(t, null);
        }

        /** @todo 1.9.0 fredt - move the messages to Error.java */
        public static Result newErrorResult(Exception t, String statement)
        {

            Result result = newResult(ResultConstants.ERROR);

            if (t is CoreException)
            {
                result.exception = (CoreException)t;
                result.mainString = result.exception.getMessage();
                result.subString = result.exception.getSQLState();

                if (statement != null)
                {
                    result.mainString += " in statement [" + statement + "]";
                }

                result.errorCode = result.exception.getErrorCode();
            }
            else if (t is OutOfMemoryException)
            {

                // gc() at this point may clear the memory allocated so far

                /** @todo 1.9.0 - review if it's better to gc higher up the stack */
                System.GC.Collect();

                result.exception = Error.error(ErrorCode.OUT_OF_MEMORY, t);
                result.mainString = result.exception.getMessage();
                result.subString = result.exception.getSQLState();
                result.errorCode = result.exception.getErrorCode();
            }
            else
            {
                result.exception = Error.error(ErrorCode.GENERAL_ERROR, t);
                result.mainString = result.exception.getMessage() + " "
                                    + t.Message;
                result.subString = result.exception.getSQLState();
                result.errorCode = result.exception.getErrorCode();

                if (statement != null)
                {
                    result.mainString += " in statement [" + statement + "]";
                }
            }

            return result;
        }

        public virtual void write(Stream dataOut,
                          RowOutputInterface rowOut)
        {

            rowOut.reset();
            rowOut.writeByte(mode);

            int startPos = rowOut.size();

            rowOut.writeSize(0);

            switch (mode)
            {

                case ResultConstants.GETSESSIONATTR:
                    rowOut.writeByte((byte)statementReturnType);
                    break;

                case ResultConstants.DISCONNECT:
                case ResultConstants.RESETSESSION:
                case ResultConstants.STARTTRAN:
                    break;

                case ResultConstants.PREPARE:
                    rowOut.writeByte((byte)statementReturnType);
                    rowOut.writeString(mainString);
                    rowOut.writeShort((short)rsScrollability);
                    rowOut.writeShort((short)rsConcurrency);
                    rowOut.writeShort((short)rsHoldability);
                    rowOut.writeByte((byte)generateKeys);

                    if (generateKeys == ResultConstants
                            .RETURN_GENERATED_KEYS_COL_NAMES || generateKeys == ResultConstants
                            .RETURN_GENERATED_KEYS_COL_INDEXES)
                    {
                        generatedMetaData.write(rowOut);
                    }
                    break;

                case ResultConstants.FREESTMT:
                    rowOut.writeLong(statementID);
                    break;

                case ResultConstants.CLOSE_RESULT:
                    rowOut.writeLong(id);
                    break;

                case ResultConstants.EXECDIRECT:
                    rowOut.writeInt(updateCount);
                    rowOut.writeInt(fetchSize);
                    rowOut.writeByte((byte)statementReturnType);
                    rowOut.writeString(mainString);
                    rowOut.writeShort((short)rsScrollability);
                    rowOut.writeShort((short)rsConcurrency);
                    rowOut.writeShort((short)rsHoldability);
                    rowOut.writeShort((short)queryTimeout);
                    rowOut.writeByte((byte)generateKeys);

                    if (generateKeys == ResultConstants
                            .RETURN_GENERATED_KEYS_COL_NAMES || generateKeys == ResultConstants
                            .RETURN_GENERATED_KEYS_COL_INDEXES)
                    {
                        generatedMetaData.write(rowOut);
                    }
                    break;

                case ResultConstants.CONNECT:
                    rowOut.writeString(databaseName);
                    rowOut.writeString(mainString);
                    rowOut.writeString(subString);
                    rowOut.writeString(zoneString);
                    rowOut.writeInt(updateCount);
                    break;

                case ResultConstants.ERROR:
                    rowOut.writeString(mainString);
                    rowOut.writeString(subString);
                    rowOut.writeInt(errorCode);
                    break;

                case ResultConstants.CONNECTACKNOWLEDGE:
                    rowOut.writeInt(databaseID);
                    rowOut.writeLong(sessionID);
                    break;

                case ResultConstants.UPDATECOUNT:
                    rowOut.writeInt(updateCount);
                    break;

                case ResultConstants.ENDTRAN:
                    {
                        int type = getActionType();

                        rowOut.writeInt(type);                     // endtran type

                        switch (type)
                        {

                            case ResultConstants.TX_SAVEPOINT_NAME_RELEASE:
                            case ResultConstants.TX_SAVEPOINT_NAME_ROLLBACK:
                                rowOut.writeString(mainString);    // savepoint name
                                break;

                            case ResultConstants.TX_COMMIT:
                            case ResultConstants.TX_ROLLBACK:
                            case ResultConstants.TX_COMMIT_AND_CHAIN:
                            case ResultConstants.TX_ROLLBACK_AND_CHAIN:
                                break;

                            default:
                                throw Error.runtimeError(ErrorCode.U_S0500, "Result");
                        }

                        break;
                    }
                case ResultConstants.PREPARE_ACK:
                    rowOut.writeByte((byte)statementReturnType);
                    rowOut.writeLong(statementID);
                    rowOut.writeShort((short)rsScrollability);
                    rowOut.writeShort((short)rsConcurrency);
                    rowOut.writeShort((short)rsHoldability);
                    metaData.write(rowOut);
                    parameterMetaData.write(rowOut);
                    break;

                case ResultConstants.CALL_RESPONSE:
                    rowOut.writeInt(updateCount);
                    rowOut.writeInt(fetchSize);
                    rowOut.writeLong(statementID);
                    rowOut.writeShort((short)(rsScrollability));
                    rowOut.writeShort((short)rsConcurrency);
                    rowOut.writeShort((short)rsHoldability);
                    metaData.write(rowOut);
                    navigator.writeSimple(rowOut, metaData);
                    break;

                case ResultConstants.EXECUTE:
                    rowOut.writeInt(updateCount);
                    rowOut.writeInt(fetchSize);
                    rowOut.writeLong(statementID);
                    rowOut.writeShort((short)rsScrollability);
                    rowOut.writeShort((short)rsConcurrency);
                    rowOut.writeShort((short)rsHoldability);
                    rowOut.writeShort((short)queryTimeout);
                    navigator.writeSimple(rowOut, metaData);
                    break;

                case ResultConstants.UPDATE_RESULT:
                    rowOut.writeLong(id);
                    rowOut.writeInt(getActionType());
                    metaData.write(rowOut);
                    navigator.write(rowOut, metaData);
                    break;

                case ResultConstants.BATCHEXECRESPONSE:
                case ResultConstants.BATCHEXECUTE:
                case ResultConstants.BATCHEXECDIRECT:
                case ResultConstants.SETSESSIONATTR:
                    {
                        rowOut.writeInt(updateCount);
                        rowOut.writeInt(fetchSize);
                        rowOut.writeLong(statementID);
                        rowOut.writeShort((short)queryTimeout);
                        metaData.write(rowOut);
                        navigator.writeSimple(rowOut, metaData);

                        break;
                    }
                case ResultConstants.PARAM_METADATA:
                    {
                        metaData.write(rowOut);
                        navigator.write(rowOut, metaData);

                        break;
                    }
                case ResultConstants.SETCONNECTATTR:
                    {
                        int type = getConnectionAttrType();

                        rowOut.writeInt(type);                     // attr type / updateCount

                        switch (type)
                        {

                            case ResultConstants.SQL_ATTR_SAVEPOINT_NAME:
                                rowOut.writeString(mainString);    // savepoint name
                                break;

                            // case ResultConstants.SQL_ATTR_AUTO_IPD // always true
                            // default: // throw, but case never happens
                            default:
                                throw Error.runtimeError(ErrorCode.U_S0500, "Result");
                        }

                        break;
                    }
                case ResultConstants.REQUESTDATA:
                    {
                        rowOut.writeLong(id);
                        rowOut.writeInt(updateCount);
                        rowOut.writeInt(fetchSize);

                        break;
                    }
                case ResultConstants.DATAROWS:
                    metaData.write(rowOut);
                    navigator.write(rowOut, metaData);
                    break;

                case ResultConstants.DATAHEAD:
                case ResultConstants.DATA:
                    rowOut.writeLong(id);
                    rowOut.writeInt(updateCount);
                    rowOut.writeInt(fetchSize);
                    rowOut.writeShort((short)rsScrollability);
                    rowOut.writeShort((short)rsConcurrency);
                    rowOut.writeShort((short)rsHoldability);
                    metaData.write(rowOut);
                    navigator.write(rowOut, metaData);
                    break;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "Result");
            }

            rowOut.writeIntData(rowOut.size() - startPos, startPos);
            dataOut.Write(rowOut.getOutputStream().getBuffer(), 0, rowOut.size());

            int count = getLobCount();
            Result current = this;

            for (int i = 0; i < count; i++)
            {
                ResultLob lob = current.lobResults;

                lob.writeBody(new DataOutputStream(dataOut));

                current = current.lobResults;
            }

            if (chainedResult == null)
            {
                dataOut.WriteByte(ResultConstants.NONE);
            }
            else
            {
                chainedResult.write(dataOut, rowOut);
            }

            dataOut.Flush();
        }

        public int getType()
        {
            return mode;
        }

        public bool isData()
        {
            return mode == ResultConstants.DATA
                   || mode == ResultConstants.DATAHEAD;
        }

        public bool isError()
        {
            return mode == ResultConstants.ERROR;
        }

        public bool isWarning()
        {
            return mode == ResultConstants.WARNING;
        }

        public bool isUpdateCount()
        {
            return mode == ResultConstants.UPDATECOUNT;
        }

        public bool isSimpleValue()
        {
            return mode == ResultConstants.VALUE;
        }

        public bool hasGeneratedKeys()
        {
            return mode == ResultConstants.UPDATECOUNT && chainedResult != null;
        }

        public CoreException getException()
        {
            return exception;
        }

        public long getStatementID()
        {
            return statementID;
        }

        public void setStatementID(long statementId)
        {
            this.statementID = statementId;
        }

        public String getMainString()
        {
            return mainString;
        }

        public void setMainString(String sql)
        {
            this.mainString = sql;
        }

        public String getSubString()
        {
            return subString;
        }

        public String getZoneString()
        {
            return zoneString;
        }

        public int getErrorCode()
        {
            return errorCode;
        }

        public Object getValueObject()
        {
            return valueData;
        }

        public void setValueObject(Object value)
        {
            valueData = value;
        }

        public Statement getStatement()
        {
            return statement;
        }

        public void setStatement(Statement statement)
        {
            this.statement = statement;
        }

        public String getDatabaseName()
        {
            return databaseName;
        }

        public void setMaxRows(int count)
        {
            updateCount = count;
        }

        public int getFetchSize()
        {
            return this.fetchSize;
        }

        public void setFetchSize(int count)
        {
            fetchSize = count;
        }

        public int getUpdateCount()
        {
            return updateCount;
        }

        public int getConnectionAttrType()
        {
            return updateCount;
        }

        public void setConnectionAttrType(int type)
        {
            updateCount = type;
        }

        public int getActionType()
        {
            return updateCount;
        }

        public void setActionType(int type)
        {
            updateCount = type;
        }

        public long getSessionId()
        {
            return sessionID;
        }

        public void setSessionId(long id)
        {
            sessionID = id;
        }

        public void setSession(SessionInterface session)
        {

            if (navigator != null)
            {
                navigator.setSession(session);
            }
        }

        public int getDatabaseId()
        {
            return databaseID;
        }

        public void setDatabaseId(int id)
        {
            databaseID = id;
        }

        public long getResultId()
        {
            return id;
        }

        public void setResultId(long id)
        {

            this.id = id;

            if (navigator != null)
            {
                navigator.setId(id);
            }
        }

        public void setUpdateCount(int count)
        {
            updateCount = count;
        }

        public void setAsTransactionEndRequest(int subType, String savepoint)
        {

            mode = ResultConstants.ENDTRAN;
            updateCount = subType;
            mainString = savepoint == null ? ""
                                            : savepoint;
        }

        public Object[] getSingleRowData()
        {

            Object[] data = (Object[])initialiseNavigator().getNext();

            data = (Object[])ArrayUtil.resizeArrayIfDifferent(data,
                    metaData.getColumnCount());

            return data;
        }

        public Object[] getParameterData()
        {
            return ((RowSetNavigatorClient)navigator).getData(0);
        }

        public Object[] getSessionAttributes()
        {
            return (Object[])initialiseNavigator().getNext();
        }

        public void setResultType(int type)
        {
            mode = (byte)type;
        }

        public void setStatementType(int type)
        {
            statementReturnType = type;
        }

        public int getStatementType()
        {
            return statementReturnType;
        }

        public int getGeneratedResultType()
        {
            return generateKeys;
        }

        public ResultMetaData getGeneratedResultMetaData()
        {
            return generatedMetaData;
        }

        public Result getChainedResult()
        {
            return chainedResult;
        }

        public Result getUnlinkChainedResult()
        {

            Result result = chainedResult;

            chainedResult = null;

            return result;
        }

        public void addChainedResult(Result result)
        {

            Result current = this;

            while (current.chainedResult != null)
            {
                current = current.chainedResult;
            }

            current.chainedResult = result;
        }

        public void addWarnings(CoreException[] warnings)
        {

            for (int i = 0; i < warnings.Length; i++)
            {
                Result warning = newWarningResult(warnings[i]);

                addChainedResult(warning);
            }
        }

        public int getLobCount()
        {
            return lobCount;
        }

        public ResultLob getLOBResult()
        {
            return lobResults;
        }

        public void addLobResult(ResultLob result)
        {

            Result current = this;

            while (current.lobResults != null)
            {
                current = current.lobResults;
            }

            current.lobResults = result;

            lobCount++;
        }

        public void clearLobResults()
        {
            lobResults = null;
            lobCount = 0;
        }

        //----------- Navigation
        RowSetNavigator navigator;

        public RowSetNavigator getNavigator()
        {
            return navigator;
        }

        public void setNavigator(RowSetNavigator navigator)
        {
            this.navigator = navigator;
        }

        public RowSetNavigator initialiseNavigator()
        {

            switch (mode)
            {

                case ResultConstants.BATCHEXECUTE:
                case ResultConstants.BATCHEXECDIRECT:
                case ResultConstants.BATCHEXECRESPONSE:
                case ResultConstants.EXECUTE:
                case ResultConstants.UPDATE_RESULT:
                case ResultConstants.SETSESSIONATTR:
                case ResultConstants.PARAM_METADATA:
                    navigator.beforeFirst();

                    return navigator;

                case ResultConstants.DATA:
                case ResultConstants.DATAHEAD:
                    navigator.reset();

                    return navigator;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "Result");
            }
        }
    }
}
